package top.codingfly.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.codingfly.netty.common.*;
import top.codingfly.netty.utils.CommonUtil;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 很大的误区:
 *   误区一：设计心跳检测时，一定要先配置空闲检测 new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS),
 *          让netty框架超时触发超时事件，pipeline后面的ChannelHandler才可以接收到事件，
 *          如果发心跳逻辑的ChannelHandler继承IdleStateHandler，配置了时间间隔，
 *          但是该类无法接收到心跳事件，只有该类之后pipeline的的ChannelHandler可以接收到超时事件
 */
public class NettyClient {
    public boolean manualClose = false; // 是否手动关闭, 手动关闭后, 不再重连, 表示永久关闭
    public String ipHost;
    public Integer port;
    public NettyHandler nettyHandler;
    public EventLoopGroup eventLoopGroup = null;
    public boolean autoReConnect = true;
    public LogLevel logLevel;
    public NettyPacket heartbeatPacket = null;
    public Integer heartbeatTimeout = 30; // 心跳时间默认30秒
    public int workThreadCount = 3;
    public Channel channel;
    private final static Logger logger = LoggerFactory.getLogger(NettyClient.class);

    public NettyClient(NettyHandler nettyHandler, NettyPacket heartbeatPacket, LogLevel logLevel, boolean autoReConnect) {
        if (nettyHandler ==null) {
            throw new RuntimeException("nettyHandler not allow null");
        }
        this.logLevel = logLevel;
        this.autoReConnect = autoReConnect;
        this.heartbeatPacket = heartbeatPacket;
        this.nettyHandler = nettyHandler;
    }

    public NettyClient(NettyHandler nettyHandler, NettyPacket heartbeatPacket, LogLevel logLevel, int workThreadCount, boolean autoReConnect) {
        if (nettyHandler ==null) {
            throw new RuntimeException("nettyHandler not allow null");
        }
        this.logLevel = logLevel;
        this.autoReConnect = autoReConnect;
        this.heartbeatPacket = heartbeatPacket;
        this.workThreadCount = workThreadCount;
        this.nettyHandler = nettyHandler;
    }

    public void connect(String ipHost, Integer port) throws InterruptedException {
        this.port = port;
        this.ipHost = ipHost;
        Bootstrap bootstrap = new Bootstrap();
        this.eventLoopGroup = new NioEventLoopGroup(workThreadCount);
        bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>(){
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                if (logLevel!=null) { // 无需打印netty框架的日志, netty框架几乎很少bug，即使有，自己没能力改netty的源码
//                    pipeline.addLast(new LoggingHandler(logLevel));
                }
                /**
                 * 很大的误区：设计心跳检测时，一定要先配置空闲检测 new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS),
                 *            只需要设置一个写超时或者读都超时就可以了，否则读写超时各触发一个事件，导致发了两个心跳包(没什么意义)，
                 *            让netty框架超时触发超时事件，pipeline后面的ChannelHandler才可以接收到事件，
                 *            如果发心跳逻辑的ChannelHandler继承IdleStateHandler，配置了时间间隔，
                 *            但是该类无法接收到心跳事件，只有该类之后pipeline的的ChannelHandler可以接收到超时事件
                 */
                if (heartbeatPacket!=null) {
                    pipeline.addLast(new IdleStateHandler(0, heartbeatTimeout, 0, TimeUnit.SECONDS)); // 先添加空闲检测
//                    new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS) 参数含义：
//                    // 第1个参数表示: 读空闲时间为 5 秒（readerIdleTime）
//                    // 第2个参数表示: 写空闲时间为 0（不检测）
//                    // 第3个参数表示: 读写空闲时间为 0（不检测）
                }
                pipeline.addLast(nettyDecoder());  // 每次都要创建新的对象
                pipeline.addLast(createHandler()); // 每次都要创建新的对象
                pipeline.addLast(nettyEncoder());  // 每次都要创建新的对象
                if (heartbeatPacket!=null) { // 设置超时处理逻辑代码
                    pipeline.addLast(new ClientHeartbeatHandler());
                }
            }
        });
        try {
            ChannelFuture channelFuture = bootstrap.connect(ipHost, port).sync();// 在这里不阻塞
            channel = channelFuture.channel();
//            channel.closeFuture().sync(); // 这一行是阻塞代码
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
            throw e;
        }
    }

    /** 移除没用的东西 */
    public void destroyNoUseData() {
        closeChannel();
        this.channel = null;
        closeEventLoopGroup();
        this.eventLoopGroup = null;
    }

    public void close() {
        manualClose = true;
        destroyNoUseData();
    }

    /** 重连方法改成private, 内部重连 */
    private void innerReConnect() {
        if (manualClose) {
            logger.info("主动关闭, 不重连");
            return;
        }
        if (channel == null || channel.isActive()==false) {
            synchronized (this) {
                while (true) { // 不断重连, 直到成功
                    if (channel == null || channel.isActive()==false) {
                        try {
                            destroyNoUseData(); // 销毁上一个连接对应的线程池和资源
                            connect(ipHost, port);
                            break; // 重连不抛异常, 表示成功, 成功才退出
                        } catch (Exception e) {
                            CommonUtil.sleep(5000L);
                            logger.error(e.getMessage(), e);
                        }
                    } else {
                        break;
                    }
                }
            }
        }
    }

    private ByteToMessageDecoder nettyDecoder() {
        return new ByteToMessageDecoder() {
            @Override
            protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
                nettyHandler.decode(ctx, in, out);
            }
        };
    }

    private MessageToByteEncoder nettyEncoder() {
        return new MessageToByteEncoder() {
            @Override
            protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
                nettyHandler.encode(ctx, msg, out);
            }
        };
    }

    private ChannelInboundHandlerAdapter createHandler() {
        return new ChannelInboundHandlerAdapter() {

            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                nettyHandler.handle(ctx, msg);
            }

            @Override
            public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                nettyHandler.channelRegistered(ctx);
            }

            @Override
            public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
                nettyHandler.channelUnregistered(ctx);
            }

            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                nettyHandler.channelActive(ctx);
            }

            @Override
            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                nettyHandler.channelInactive(ctx);
                destroyNoUseData(); // 服务端主动调ChannelHandlerContext.channel.close()关闭客户端连接时, 会触发这个channelInactive方法
                if (autoReConnect) {
                    innerReConnect();
                }
            }

            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                nettyHandler.exceptionCaught(ctx, cause);
                destroyNoUseData();
                if (autoReConnect) {
                    innerReConnect();
                }
            }
        };
    }

    private void closeEventLoopGroup() {
        if (eventLoopGroup != null) {
            try {
                eventLoopGroup.shutdownGracefully();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void closeChannel() {
        if (channel != null) {
            try {
                channel.close();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    // 发消息
    public void send(NettyPacket msg) {
        channel.writeAndFlush(msg);
    }

    private class ClientHeartbeatHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;

                // 处理读空闲事件
                if (event.state() == IdleState.READER_IDLE || event.state() == IdleState.WRITER_IDLE || event.state() == IdleState.ALL_IDLE) {
                    if (heartbeatPacket!=null) {
                        logger.info("schedule task send heartbeat message");
                        ctx.writeAndFlush(heartbeatPacket);
                    }
                }
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }

    }

}
